package com.catazzz.mall.common.config;

import com.catazzz.mall.common.domain.SwaggerProperties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @作者: Tao
 * @日期: 2025/4/23
 * @时间: 15:00
 * @描述: Swagger基础配置 <pre>
 */
public abstract class BaseSwaggerConfig {

    /**
     * 创建Swagger API文档的配置
     *
     * @return Docket实例，用于构建Swagger文档
     */
    @Bean
    public Docket createRestApi() {
        SwaggerProperties swaggerProperties = swaggerProperties();
        // 创建并配置Swagger2的API文档生成Docket对象
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                // 设置API文档的信息
                .apiInfo(apiInfo(swaggerProperties))
                .select()
                // 指定扫描的API基础包路径，以便自动发现并文档化接口
                .apis(RequestHandlerSelectors.basePackage(swaggerProperties.getApiBasePackage()))
                // 配置路径选择器，这里选择所有路径
                .paths(PathSelectors.any())
                .build();
        if (swaggerProperties.isEnableSecurity()) {
            // 添加登录认证
            docket.securitySchemes(securitySchemes()).securityContexts(securityContexts());
        }
        return docket;
    }

    /**
     * 构建API文档的详细信息
     *
     * @return ApiInfo实例，包含API文档的标题、描述、联系人等信息
     */
    private ApiInfo apiInfo(SwaggerProperties swaggerProperties) {
        // 构建并返回一个ApiInfo实例，用于配置Swagger API文档的信息
        return new ApiInfoBuilder()
                // 设置API文档的标题
                .title(swaggerProperties.getTitle())
                // 设置API文档的描述
                .description(swaggerProperties.getDescription())
                // 设置API文档的联系人信息，包括名称、网址和邮箱
                .contact(new Contact(swaggerProperties.getContactName(), swaggerProperties.getContactUrl(), swaggerProperties.getContactEmail()))
                // 设置API文档的版本
                .version(swaggerProperties.getVersion())
                // 完成ApiInfo对象的构建
                .build();
    }

    /**
     * 获取安全方案列表
     * <p>
     * 此方法用于配置和返回一个包含安全方案的列表，用于应用中的安全认证
     * 目前仅支持通过API密钥方式进行认证，因此列表中仅包含一个ApiKey实例
     *
     * @return List<SecurityScheme> 返回一个包含安全方案的列表，这里仅包含一个ApiKey实例
     */
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> result = new ArrayList<>();

        // 创建一个ApiKey实例，参数分别为名称、值和放置位置
        // "Authorization" 是API密钥的名称，也是HTTP请求头中的键名
        // "header" 表示API密钥值将被放置在HTTP请求的头部
        ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");

        // 将创建的ApiKey实例添加到安全方案列表中
        result.add(apiKey);

        return result;
    }

    /**
     * 配置安全上下文信息
     * <p>
     * 该方法用于构建和配置应用程序中的安全上下文这些安全上下文定义了哪些路径需要受到保护，
     * 以及使用哪种安全方案进行保护在这个例子中，它配置了匹配 "/brand/.*" 路径的所有请求
     * 都需要进行身份验证
     *
     * @return 返回一个包含配置好的安全上下文的列表
     */
    private List<SecurityContext> securityContexts() {
        List<SecurityContext> result = new ArrayList<>();

        // 构建一个安全上下文，指定其安全引用和适用路径
        SecurityContext securityContext = SecurityContext.builder()
                // 设置该安全上下文使用默认的身份验证方式
                .securityReferences(defaultAuth())
                // 指定该安全上下文应用于匹配正则表达式 "/brand/.*" 的路径
//                .forPaths(PathSelectors.regex("/brand/.*"))
                .operationSelector(oc -> oc.requestMappingPattern().matches("/*/.*"))
                .build();

        result.add(securityContext);

        return result;
    }

    /**
     * 创建默认的认证信息
     * 该方法用于生成一个包含全局认证作用域的列表，用于授权
     *
     * @return 包含默认认证信息的列表
     */
    private List<SecurityReference> defaultAuth() {
        List<SecurityReference> result = new ArrayList<>();

        // 创建一个全局认证作用域，名称为"global"，描述为"accessEverything"
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");

        // 初始化一个包含单个认证作用域的数组
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;

        // 向结果列表中添加一个认证引用，认证名称为"Authorization"，包含之前创建的认证作用域
        result.add(new SecurityReference("Authorization", authorizationScopes));

        return result;
    }

    /**
     * 修改Springfox处理映射以适应高版本的Spring Boot
     *
     * @return BeanPostProcessor实例，用于处理Springfox的初始化
     */
    @Bean
    public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
        return new BeanPostProcessor() {

            /**
             * 在Bean初始化后进行后处理
             *
             * @param bean 初始化后的Bean实例
             * @param beanName Bean的名称
             * @return 处理后的Bean实例
             * @throws BeansException 如果处理过程中发生异常
             * 此方法主要用于在Bean初始化完成后进行一些定制操作这里特别关注的是
             * WebMvcRequestHandlerProvider和WebFluxRequestHandlerProvider类型的Bean
             * 如果Bean是这两种类型之一，将调用customizeSpringfoxHandlerMappings方法
             * 对其处理映射进行定制，这是为了在Springfox中调整HandlerMappings，
             * 以便根据需要修改请求处理映射
             */
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
                    customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
                }
                return bean;
            }

            /**
             * 过滤掉使用了PatternParser的处理映射
             *
             * @param mappings 映射列表，包含请求处理映射
             */
            private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
                if (mappings == null) {
                    return;
                }

                List<T> filteredMappings = mappings.stream()
                        .filter(mapping -> mapping.getPatternParser() == null)
                        .collect(Collectors.toList());

                mappings.clear();
                mappings.addAll(filteredMappings);
            }


            /**
             * 获取处理映射
             *
             * @param bean 当前处理的Bean实例
             * @return 映射列表，可能为null如果找不到相应的字段
             */
            @SuppressWarnings("unchecked")
            private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
                try {
                    Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
                    if (field == null) {
                        return null;
                    }
                    field.setAccessible(true);
                    Object handlerMappings = field.get(bean);
                    if (handlerMappings == null) {
                        return null;
                    }
                    return (List<RequestMappingInfoHandlerMapping>) handlerMappings;
                } catch (Exception e) {
                    return null;
                }
            }
        };
    }

    /**
     * 自定义Swagger配置
     */
    public abstract SwaggerProperties swaggerProperties();
}
